;name: keyfilter.asm
;
;description: The program displays the ASCII code in hexadecimal of the pressed key.
;
;
;build: nasm -felf64 keyfilter.asm -o keyfilter.o
;       ld -melf_x86_64 keyfilter.o -o keyfilter
;
;remark: On terminals with escape key sequences binded to keys (like the cursor keys) the
;        program exits after pressing those (since ESCAPE sequences starts with the ESC key)
;
;source: 11.7 Writing UNIX® Filters - FreeBSD Developers’ Handbook

bits 64

%include "unistd.inc"
%include "sys/termios.inc"

section .bss

    buffer:     resq    8
    .length:    equ     $-buffer
    
section .rodata

    intro:      db      "filter - by Agguro 2011 source:  FreeBSD Developers’ Handbook.", 10
                db      "The program shows the ASCII codes of the pressed keys. ESC terminates the "
                db      "program with key code or CTRL-C without.", 10
                db      "start typing >> "
    .length:    equ     $-intro      
    EOL:
    .start:     db      "1B", 10      ;print the ASCII code for ESC to be complete
    .end:

section .data

    output:     db      0,0," "
    .length:    equ     $-output

    TERMIOS termios                   ;termios structure
    
section .text

global _start
_start:

    mov     rsi,intro
    mov     rdx,intro.length
    call    Write
    call    termios_canonical_mode_off          ;switch canonical mode off
    call    termios_echo_mode_off               ;no echo

getKeyStroke:
    syscall read,stdin,buffer,buffer.length 
    cmp     byte[buffer],0x1B                   ;if ESC pressed
    je      Exit
    mov     al,byte[buffer]
  
toASCII:
    shl     rax,4                               ;most significant nibble in AH
    shr     al,4                                ;least significant nibble in AL
    or      ax,3030h                            ;attempt to convert both to ASCII
    cmp     al,"9"                              ;is AL = ascii 9 ?
    jle     .highNibble
    add     al,7
.highNibble:
    cmp     ah,"9"
    jle     .done
    add     ah,7
.done:
    ror     ax,8                                ;little ENDIAN notation
    mov     word[output],ax                     ;ASCII in buffer to print
    mov     rsi,output
    mov     rdx,output.length
    call    Write
    jmp     getKeyStroke
  
Exit:
    mov     rsi,EOL
    mov     rdx,EOL.end-EOL.start
    call    Write     
    call    termios_canonical_mode_on           ;switch canonical mode back on
    call    termios_echo_mode_on                ;restore echo
    syscall exit,0

;write to STDOUT
Write:
    push    rcx
    push    rax
    push    rdi
    syscall write,stdout
    pop     rdi
    pop     rax
    pop     rcx
    ret

;subroutine to switch canonical mode on
;RAX is unchanged on exit
termios_canonical_mode_on:
    push    rax
    mov     rax,ICANON
    jmp     termios_set_localmode_flag
  
;subroutine to switch echo mode on
;RAX is unchanged on exit
termios_echo_mode_on:
    push    rax
    mov     rax,ECHO
    jmp     termios_set_localmode_flag
  
;subroutine to set the bits in the c_lflag stored in EAX
;RAX is unchanged on exit
termios_set_localmode_flag:
    push    rax
    call    termios_stdin_read
    or      dword[termios.c_lflag],eax
    call    termios_stdin_write
    pop     rax
    pop     rax
    ret
  
;subroutine to switch canonical mode off
;RAX is unchanged on exit
termios_canonical_mode_off:
    push    rax
    mov     rax,ICANON
    jmp     termios_clear_localmode_flag
  
;subroutine to switch echo mode off
;RAX is unchanged on exit
termios_echo_mode_off:
    push    rax
    mov     rax,ECHO
    jmp     termios_clear_localmode_flag
  
;subroutine to clear the bits in the c_lflag stored in EAX
;RAX is unchanged on exit
termios_clear_localmode_flag:
    push    rax
    call    termios_stdin_read
    not     eax
    and     [termios.c_lflag],eax
    call    termios_stdin_write
    pop     rax
    pop     rax
    ret
  
;subroutine for all TCGETS operation on the syscall IOCTL
;the original value of RCX is restored on exit
termios_stdin_read:
    push    rsi
    mov     rsi,TCGETS
    jmp     termios_stdin_syscall
  
;subroutine for all TCSETS operation on the syscall IOCTL
;the original value of RCX is restored on exit
termios_stdin_write:
    push    rsi
    mov     rsi,TCSETS
    jmp     termios_stdin_syscall
    
;subroutine for operations on the syscall IOCTL for STDIN
;all registers are restored to their original values on exit of the subroutine
termios_stdin_syscall:
    push    rax
    push    rdi
    push    rdx
    mov     rdx,termios
    syscall ioctl,stdin
    pop     rdx
    pop     rdi
    pop     rax
    pop     rsi
    ret
